﻿using AppWebApi.Models;
using AppWebApi.Models.Dtos;
using AppWebApi.Models.Dtos.ManagerDtos;
using AppWebApi.Models.Dtos.PersonDtos;
using AppWebApi.Models.Form;
using AppWebApi.Utilities.SM4;
using AutoMapper;
using Modules;
using Modules.Base;
using Modules.CheckIn;
using Modules.CheckIn.Entitys;
using Modules.CheckInCount;
using Modules.Corp;
using Modules.Device;
using Modules.Device.Entitys;
using Modules.FaceScan;
using Modules.FaceScan.Entitys;
using Modules.Manager;
using Modules.Person;
using Modules.Project;
using Modules.Project.Entitys;
using Modules.System.Security;
using Modules.System.Settings;
using Modules.System.Settings.Entitys;
using Modules.System.User;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Pipelines.Sockets.Unofficial.Arenas;
using PmSoft;
using PmSoft.ApplicationContext;
using PmSoft.Caching.Redis;
using PmSoft.Entity;
using PmSoft.Exceptions;
using PmSoft.FileStorage;
using PmSoft.Utilities;
using Quartz.Util;
using RestSharp;
using System.Text.Json;
using System.Web;
using static Modules.CheckIn.CheckInDetailConstants;
using static Modules.Device.CheckInDeviceConstants;
using static Modules.Person.PersonConstants;
using static Modules.Project.ProjectConstants;

namespace AppWebApi.Services
{
    [UseDI(ServiceLifetime.Scoped)]
    public class AppService
    {
        private readonly IConfiguration configuration;
        private readonly ProjectGroupMemberService projectGroupMemberService;
        private readonly ProjectService projectService;
        private readonly ProjectGroupService projectGroupService;
        private readonly CorpService corpService;
        private readonly PersonService personService;
        private readonly IApplicationContext applicationContext;
        private readonly FaceScanService faceScanService;
        private readonly SecurityService securityService;
        private readonly IMapper mapper;
        private readonly ManagerService managerService;
        private readonly ProjectPositionService projectPositionService;
        private readonly FacePhotoService personFaceService;
        private readonly RestClient client;
        private readonly PmSoftRedisCache redisCache;
        private readonly IFileStorage fileStorage;
        private readonly CheckInDetailService checkInDetailService;
        private readonly CheckinWorkHoursService checkinWorkHoursService;
        private readonly ILogger logger;
        private readonly CheckinDeviceService checkinDeviceService;
        private readonly CheckinDeviceCommandService checkinDeviceCommandService;

        public AppService(IConfiguration configuration, ProjectGroupMemberService projectGroupMemberService, ProjectService projectService, ProjectGroupService projectGroupService, CorpService corpService, PersonService personService, IApplicationContext applicationContext, FaceScanService faceScanService, SecurityService securityService, IMapper mapper, ManagerService managerService, ProjectPositionService projectPositionService, FacePhotoService personFaceService, PmSoftRedisCache redisCache, IFileStorage fileStorage, CheckInDetailService checkInDetailService, CheckinWorkHoursService checkinWorkHoursService, ILogger<AppService> logger, CheckinDeviceService checkinDeviceService, CheckinDeviceCommandService checkinDeviceCommandService)
        {
            this.configuration = configuration;
            this.projectGroupMemberService = projectGroupMemberService;
            this.projectService = projectService;
            this.projectGroupService = projectGroupService;
            this.corpService = corpService;
            this.personService = personService;
            this.applicationContext = applicationContext;
            this.faceScanService = faceScanService;
            this.securityService = securityService;
            this.mapper = mapper;
            this.managerService = managerService;
            this.projectPositionService = projectPositionService;
            this.personFaceService = personFaceService;
            this.redisCache = redisCache;
            this.fileStorage = fileStorage;
            this.checkInDetailService = checkInDetailService;
            this.checkinWorkHoursService = checkinWorkHoursService;
            this.checkinDeviceService = checkinDeviceService;
            client = new RestClient(configuration.GetValue<string>("FaceRecognitionWebUrl"));
            this.logger = logger;
            this.checkinDeviceCommandService = checkinDeviceCommandService;
        }

        public ManagerInfo ManagerCardLogin(CaredNoLoginInfo loginInfo)
        {
            if (string.IsNullOrEmpty(loginInfo.CardNumber)) throw new BusinessException("登录账号不能为空");
            if (string.IsNullOrEmpty(loginInfo.PassWord)) throw new BusinessException("登录密码不能为空");
            if (string.IsNullOrEmpty(loginInfo.ScanCode)) throw new BusinessException("人脸识别代码不能为空");

            if (!faceScanService.Check(loginInfo.CardNumber, loginInfo.ScanCode, FaceScanReferType.Login, 数据来源.APP))
                throw new BusinessException("未通过人脸识别");

            ManagerInfo? manager = managerService.PasswordSignIn(loginInfo.CardNumber, SecurityPasswordHelper.EncodePassword(loginInfo.PassWord, UserPasswordFormat.SM3));
            if (manager == null) throw new BusinessException("账号不存在");
            return manager;
        }

        /// <summary>
        /// 工人登录
        /// </summary>
        /// <param name="loginInfo"></param>
        /// <returns></returns>
        /// <exception cref="BusinessException"></exception>
        public string WorkerCardLogin(CaredNoLoginInfo loginInfo)
        {
            if (string.IsNullOrEmpty(loginInfo.CardNumber))
                throw new ArgumentNullException(nameof(loginInfo.CardNumber));
            if (string.IsNullOrEmpty(loginInfo.ScanCode))
                throw new ArgumentNullException(nameof(loginInfo.ScanCode));

            PersonInfo? personInfo = personService.GetPersonByCardNo(loginInfo.CardNumber);
            if (personInfo == null)
                throw new BusinessException("人员不存在");

            if (!faceScanService.Check(loginInfo.CardNumber, loginInfo.ScanCode, FaceScanReferType.Login, 数据来源.APP))
                throw new BusinessException("未通过人脸识别");

            SecurityEditForm form = new();
            form.LastLoginIp = applicationContext.ClientIp;
            form.LastLoginTime = DateTime.Now;
            form.UserType = UserType.Worker;
            form.IsAuth = true;
            SysSecurity? sysSecurity = new SysSecurity();
            if (personInfo.SecurityId.HasValue)
                sysSecurity = securityService.GetSysSecurity(personInfo.SecurityId.Value);
            if (sysSecurity == null)
            {
                //新增
                sysSecurity = new SysSecurity();
                mapper.Map(form, sysSecurity);
                sysSecurity = securityService.Create(sysSecurity);
            }
            else
            {
                //更新
                sysSecurity.LastLoginIp = applicationContext.ClientIp ?? "";
                sysSecurity.LastLoginTime = DateTime.Now;
                sysSecurity.IsAuth = true;
                securityService.Update(sysSecurity);
            }
            personInfo.SecurityId = sysSecurity.SecurityId;
            personService.UpdatePersonInfo(personInfo);
            //生成token
            string token = GetWorkerToken(sysSecurity.SecurityId, form.UserType, loginInfo.CardNumber);
            return token;
        }

        /// <summary>
        /// 生成工人token
        /// </summary>
        /// <param name="securityId"></param>
        /// <param name="userType"></param>
        /// <param name="cardNumber"></param>
        /// <returns></returns>
        public string GetWorkerToken(int securityId, UserType userType, string cardNumber)
        {
            //根据用户信息,生成token  ,源格式为:安全id|usertype|身份证|timestamp
            PersonInfo? personInfo = personService.GetPersonByCardNo(cardNumber);
            if (personInfo == null)
                throw new BusinessException("没有查询到人员信息");
            string value = $"{securityId}|{userType}|{cardNumber}|{DateTime.Now.AddDays(7)}|{personInfo.PersonId}";
            return EncryptionUtility.SymmetricEncrypt(SymmetricEncryptType.Rijndael, value);
        }

        /// <summary>
        /// 生成项目管理员token
        /// </summary>
        /// <param name="securityId"></param>
        /// <param name="userType"></param>
        /// <param name="cardNumber"></param>
        /// <returns></returns>
        public string GetManagerToken(int securityId, UserType userType, string cardNumber)
        {
            //根据用户信息,生成token  ,源格式为:安全id|usertype|身份证|timestamp
            ManagerInfo? manager = managerService.GetManagerInfo(cardNumber);
            if (manager == null)
                throw new BusinessException("没有查询到人员信息");
            string value = $"{securityId}|{userType}|{cardNumber}|{DateTime.Now.AddDays(7)}|{manager.ManagerId}";
            return EncryptionUtility.SymmetricEncrypt(SymmetricEncryptType.Rijndael, value);
        }

        /// <summary>
        /// 查询我的项目信息（工人）
        /// </summary>
        /// <param name="personId"></param>
        /// <returns></returns>
        public IEnumerable<Models.Dtos.PersonDtos.ProjectAppDto> GetProjects(int personId, bool isOnline = false)
        {
            IEnumerable<ProjectGroupMember> projectGroupMembers = projectGroupMemberService.GetProjectGroupMembersByPersonId(personId);
            if (isOnline)
            {
                projectGroupMembers = projectGroupMembers.Where(a => a.Status == 进出场状态.进场);
            }
            var projects = projectService.GetProjects(projectGroupMembers.Select(a => a.ProjectId)).Where(a => a.AuditStatus == 项目审核状态.通过);
            projectGroupMembers = projectGroupMembers.Where(a => projects.Select(x => x.ProjectId).Contains(a.ProjectId));
            var groups = projectGroupService.GetProjectGroups(projectGroupMembers.Select(a => a.GroupId));
            var corps = corpService.GetCorpInfos(projectGroupMembers.Select(a => a.CorpId));
            List<Models.Dtos.PersonDtos.ProjectAppDto> dtos = new();
            var positions = projectPositionService.GetProjectPositionByProjectIds(projects.Select(a => a.ProjectId));
            foreach (var item in projectGroupMembers)
            {
                Models.Dtos.PersonDtos.ProjectAppDto dto = new Models.Dtos.PersonDtos.ProjectAppDto();
                dto.ProjectId = item.ProjectId;
                dto.ProjectGroupMemberId = item.Id;
                dto.WorkType = item.Type?.GetDescription() ?? item.PostCategory?.GetDescription() ?? "";
                var projectInfo = projects.Where(a => a.ProjectId == item.ProjectId).FirstOrDefault();
                if (projectInfo != null)
                {
                    dto.ProjectName = projectInfo.Name;
                    dto.ProjectAddress = projectInfo.Address ?? "";
                    //dto.Lat = projectInfo.Lat;
                    //dto.Lng = projectInfo.Lng;
                }
                if (groups != null)
                    dto.GroupName = groups.FirstOrDefault(a => a.GroupId == item.GroupId)?.Name ?? "";
                if (corps != null)
                    dto.CorpName = corps.FirstOrDefault(a => a.CorpId == item.CorpId)?.Name ?? "";
                dto.Position = positions.Where(a => a.ProjectId == item.ProjectId).AsDtos();
                dtos.Add(dto);
            }
            return dtos;
        }

        public IEnumerable<Models.Dtos.ManagerDtos.ProjectAppDto> GetManagerProjects(int objectId)
        {
            var data = projectService.GetProjectInfosByManagerId(objectId).Where(a => a.AuditStatus == 项目审核状态.通过);
            var positions = projectPositionService.GetProjectPositionByProjectIds(data.Select(a => a.ProjectId));
            List<Models.Dtos.ManagerDtos.ProjectAppDto> dtos = new();
            ManagerInfo? managerInfo = managerService.GetManagerInfo(objectId);
            if (data.Any())
            {
                foreach (var item in data)
                {
                    Models.Dtos.ManagerDtos.ProjectAppDto dto = new()
                    {
                        ProjectId = item.ProjectId,
                        Name = item.Name,
                        PrjNum = item.PrjNum,
                        PositionCount = item.PositionCount,
                        Position = positions.Where(a => a.ProjectId == item.ProjectId).AsDtos()
                    };
                    if (managerInfo?.ProjectId == item.ProjectId)
                        dto.IsDefault = true;
                    dtos.Add(dto);
                }
            }
            return dtos;
        }

        /// <summary>
        /// 执行http请求
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="request"></param>
        /// <returns></returns>
        /// <exception cref="BusinessException"></exception>
        public async Task<TResult?> ExecuteAsync<TResult>(RestRequest request)
        {
            var response = await client.ExecuteAsync<TResult>(request);

            if (response.ErrorException != null)
            {
                logger.LogError(response.ErrorException, "内部通讯失败");
                throw new BusinessException("内部通讯失败");
            }
            return response.Data;
        }

        /// <summary>
        /// 更新人员人脸照片
        /// </summary>
        /// <param name="form"></param>
        /// <exception cref="BusinessException"></exception>
        /// <exception cref="ArgumentNullException"></exception>
        public async Task UpdatePersonFacePhotoAsync(PersonFaceEditForm form)
        {
            PersonInfo? personInfo = personService.GetPersonByCardNo(form.IdCard);
            if (personInfo == null) throw new BusinessException($"人员{form.IdCard}信息不存在");
            FacePhoto? personFace = personFaceService.GetPersonFaceByIdCard(form.IdCard);
            if (personFace == null) throw new BusinessException($"人员{form.IdCard}信息不存在");

            #region 人脸比对

            //获取工人的核验照片
            //var verifyPhotoData = personFaceService.GeVerifyPhotoData(form.IdCard);
            //要替换的照片
            var camFaceData = Utilities.Base64Utilities.ConverBase64(form.Base64VerifyPhotoStr);
            RestRequest request = new RestRequest("/api/v1/Recognition/InternalCompare", Method.Post);
            CompareInfoForm compareInfoForm = new()
            {
                IdCard = form.IdCard,
                PicData = form.Base64VerifyPhotoStr
            };

            request.AddBody(compareInfoForm, ContentType.Json);
            var result = await ExecuteAsync<ApiResult<FaceCompareResult>>(request);
            if (result == null)
                throw new ArgumentNullException(nameof(result));

            if (!result.success)
                throw new BusinessException(result.msg);

            if (result.data == null)
                throw new ArgumentNullException(nameof(result.data));

            #region 下发人员信息到设备
            if (result.data.IsPass)
            {
                ProjectGroupMember? projectGroupMember = projectGroupMemberService.GetProjectGroupMembersByPersonId(personInfo.PersonId).FirstOrDefault(a => a.Status == 进出场状态.进场);
                if (projectGroupMember != null)
                {
                    IEnumerable<CheckinDevice> devices = checkinDeviceService.GetProjectCheckinDevicesByProjectId(projectGroupMember.ProjectId);
                    foreach (var device in devices)
                    {
                        CheckinDeviceCommand log = new CheckinDeviceCommand()
                        {
                            Name = 考勤机命令.人员进场后下发.GetDescription(),
                            Code = 考勤机命令.人员进场后下发,
                            MessageId = Guid.NewGuid().ToString(),
                            DeviceCode = device.Code,
                            Status = 命令下发状态.待下发,
                            IsAuto = true
                        };
                        checkinDeviceCommandService.Send(log, device, new List<PersonInfo>() { personInfo });
                    }
                }
            }
            #endregion 下发人员信息到设备

            FaceScanRecord record = new();
            record.ReferType = FaceScanReferType.ManagerPersonVerification;
            record.IdCard = form.IdCard;
            record.ScanCode = "-";
            record.ConfirmTime = DateTime.Now;
            record.IsConfirmed = true;
            record.DataSource = 数据来源.APP;
            record.IsPassed = result.data.IsPass;
            record.VerifyTime = DateTime.Now;
            record.CameraPhoto = $"{form.IdCard}/Appauth_{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.jpg";

            #region 上传到minio

            await fileStorage.PutFileAsync(FileBuckets.Person, record.CameraPhoto, camFaceData);

            #endregion 上传到minio

            applicationContext.GetService<FaceScanRecordService>().Create(record);

            if (!result.data.IsPass)
                throw new BusinessException("人脸对比不通过");

            #endregion 人脸比对

            try
            {
                applicationContext.Transaction.Begin();

                //替换核验照片
                personFace.VerifyPhoto = record.CameraPhoto;
                personFace.VerifyPhotover = personFace.VerifyPhotover.HasValue ? personFace.VerifyPhotover + 1 : 1;
                personFace.VerifyPhotoTime = DateTime.Now;
                personFaceService.Update(personFace);
                //修改人员手机号
                personInfo.Mobile = form.Mobile;
                personService.UpdatePersonInfo(personInfo);

                applicationContext.Transaction.Complete();
            }
            catch (Exception ex)
            {
                applicationContext.Transaction.Abort();
                logger.LogError($"未捕获到人脸信息:{ex.Message}");
                throw new BusinessException("未捕获到人脸信息");
            }
        }

        public IEnumerable<UserEmployeeDto> GetWorkerMembers(int personId, UserEmployeeQuery query)
        {
            PersonInfo? personInfo = personService.GetPerson(personId);
            if (personInfo == null)
                throw new BusinessException("当前人员信息不存在");
            ProjectGroupMember? member = projectGroupMemberService.GetOnSiteMembers(personInfo.PersonId);
            if (member == null)
                throw new BusinessException("当前人员班组信息不存在");
            var members = projectGroupMemberService.GetProjectGroupMembers(new ProjectGroupMemberQuery() { Keywords = query.Keywords, GroupId = member.GroupId }, query.PageNum, query.PageSize);
            return members.AsDtos();
        }

        public IEnumerable<UserEmployeeDto> GetManagerMembers(int managerId, UserEmployeeQuery query)
        {
            ManagerInfo? managerInfo = managerService.GetManagerInfo(managerId);
            if (managerInfo == null)
                throw new BusinessException("当前人员信息不存在");
            var members = projectGroupMemberService.GetProjectGroupMembers(new ProjectGroupMemberQuery() { Keywords = query.Keywords, ProjectId = managerInfo.ProjectId }, query.PageNum, query.PageSize);
            return members.AsDtos();
        }

        /// <summary>
        /// 根据身份证缓存当天考勤
        /// </summary>
        /// <param name="cardNumber">人员身份证号</param>
        /// <param name="entity">需要缓存的实体</param>
        public async Task SetRedisCheckinDetails(string cardNumber, CheckInDetail entity)
        {
            string hashKey = $"checkin:{DateTime.Now.ToString("yyyy-MM-dd")}";
            List<CheckInDetail> data = await GetRedisCheckinDetails(cardNumber);
            if (data == null)
                redisCache.HashSet(hashKey, cardNumber, new List<CheckInDetail>() { entity });
            else
            {
                data.Add(entity);
                redisCache.HashSet(hashKey, cardNumber, data);
            }
        }

        /// <summary>
        /// 获取当天缓存人员考勤记录
        /// </summary>
        /// <param name="cardNumber">人员身份证号</param>
        /// <returns></returns>
        public async Task<List<CheckInDetail>> GetRedisCheckinDetails(string cardNumber)
        {
            DateTime date = DateTime.Now;
            string hashKey = $"checkin:{date.ToString("yyyy-MM-dd")}";
            var data = await redisCache.HashGetAsync<List<CheckInDetail>>(hashKey, cardNumber);
            if (data == null || data.Count() == 0)
            {
                PersonInfo? personInfo = personService.GetPersonByCardNo(cardNumber);
                if (personInfo == null)
                    throw new BusinessException("当前人员信息不存在");
                ProjectGroupMember? projectGroupMember = projectGroupMemberService.GetOnSiteMembers(personInfo.PersonId);
                if (projectGroupMember == null)
                    throw new BusinessException("当前人员没有在任何班组进场");
                data = checkInDetailService.GetCheckInDetailsByMonthAndCheckDateAndMemberId(date.Date, projectGroupMember.Id).ToList();
                redisCache.HashSet(hashKey, cardNumber, data);
            }
            return data;
        }

        /// <summary>
        /// 获取考勤照片
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        /// <exception cref="BusinessException"></exception>
        public byte[] GetCheckinPhoto(string path)
        {
            byte[] buffer = fileStorage.GetFileAsync(FileBuckets.Checkin, path).GetAwaiter().GetResult();
            return buffer;
        }

        public CheckinReportDto GetCheckinReport(int projectGroupMemberId)
        {
            var checkinWorkHoursService = applicationContext.GetService<CheckinWorkHoursService>();
            CheckinReportDto dto = new CheckinReportDto();

            int month = DateTime.Now.ToMonthNumber();
            ProjectGroupMember? projectGroupMember = projectGroupMemberService.GetMember(projectGroupMemberId);
            if (projectGroupMember == null)
                throw new BusinessException("当前工人班组信息不存在");
            PersonInfo? personInfo = personService.GetPerson(projectGroupMember.PersonId);
            if (personInfo == null)
                throw new BusinessException("当前人员信息不存在");
            CheckinWorkHours? checkinWorkHours = checkinWorkHoursService.GetCheckinWorkHoursByMonthAndMemberId(month, projectGroupMember.Id);
            dto.MonthCheckinDay = checkinWorkHours?.CheckinDay ?? 0;
            dto.MonthCheckinWorkHours = decimal.Round(checkinWorkHours?.WorkHours ?? 0, 2);
            IEnumerable<CheckInDetail> checkInDetails = GetRedisCheckinDetails(personInfo.CardNumber).GetAwaiter().GetResult();
            if (!checkInDetails.Any())
                dto.ToDayCheckinWorkHours = 0;
            else
                dto.ToDayCheckinWorkHours = decimal.Round(GetWorkHoursByDay(DateTime.Now.Date, checkInDetails), 2);
            return dto;
        }

        /// <summary>
        /// 根据日期获取工时
        /// </summary>
        /// <param name="time">日期</param>
        /// <param name="checkInDetails">日期对应的考勤记录</param>
        /// <returns></returns>
        public decimal GetWorkHoursByDay(DateTime time, IEnumerable<CheckInDetail> checkInDetails)
        {
            CheckInDetail? inCheckindetail = checkInDetails.OrderBy(a => a.CreateTime).FirstOrDefault(a => a.CheckinDate == time && a.EntryExitMark == 进出标识.进场);
            CheckInDetail? outCheckindetail = checkInDetails.OrderByDescending(a => a.CreateTime).FirstOrDefault(a => a.CheckinDate == time && a.EntryExitMark == 进出标识.出场);
            if (inCheckindetail != null && outCheckindetail != null)
            {
                return Convert.ToDecimal((outCheckindetail.CreateTime - inCheckindetail.CreateTime).TotalHours);
            }
            return 0;
        }

        public IEnumerable<CheckInDetail> GetCheckinDetailsByMemberAndMonth(int memberId, int month)
        {
            return checkInDetailService.GetCheckinDetailsByMonthAndMemberId(month, memberId);
        }

        /// <summary>
        /// 获取考勤报告
        /// </summary>
        /// <param name="projectId"></param>
        /// <returns></returns>
        public WorkerCheckinReportDto GetWorkerCheckinReport(int projectId)
        {
            var checkInStatisticService = applicationContext.GetService<CheckInStatisticService>();
            WorkerCheckinReportDto dto = new();
            DateTime date = DateTime.Now;

            var statisticInfo = checkInStatisticService.GetProjectCheckinStatisticInfo(CountStageType.Day, projectId, date);
            //获取今日考勤记录

            dto.ToDayCheckinCount = statisticInfo.CheckinCount;
            dto.ToDayCheckinPersonCount = statisticInfo.CheckinPersonCount;
            IEnumerable<ProjectGroupMember> members = projectGroupMemberService.GetProjectGroupMembersByProjectId(projectId).Where(a => a.Status == 进出场状态.进场);
            dto.ProjectOnlinePersonCount = members.Count();
            dto.ToDayAbsenteeismPersonCount = dto.ProjectOnlinePersonCount - dto.ToDayCheckinPersonCount;
            dto.ToDayAttendanceRate = dto.ProjectOnlinePersonCount > 0 ? dto.ToDayCheckinPersonCount / (decimal)dto.ProjectOnlinePersonCount : 0M;
            dto.CheckinPersonCount = statisticInfo.CheckinPersonCount;
            return dto;
        }

        public IPagedList<CheckInDetail> GetWorkerCkeckinDetails(WorkerCkeckinDetailsQuery form, int projectId)
        {
            var data = checkInDetailService.GetCheckInDetailsByMonth(new CheckInDetailMonthQuery()
            {
                Month = form.Month,
                ProjectId = projectId
            }, form.PageNum, form.PageSize);
            return data;
        }

        public CheckInDetail? GetCheckinDetail(int month, string checkinDetailId)
        {
            return checkInDetailService.GetCheckInDetail(checkinDetailId, month);
        }
    }
}